package contract

import (
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/hyperledger/fabric/bccsp"
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"github.com/hyperledger/fabric/core/chaincode/shim/ext/cid"
	"github.com/hyperledger/fabric/core/chaincode/shim/ext/entities"
)

type Context struct {
	stub  shim.ChaincodeStubInterface
	clientID cid.ClientIdentity
	args  map[string]interface{}
	participants map[string]Participant
	bccspInst  bccsp.BCCSP
}

func initContext(stub shim.ChaincodeStubInterface, contract Contract, args map[string]interface{}) (*Context, error) {
	clientID, err := cid.New(stub)
	if err != nil {
		return nil, err
	}
	return &Context{stub:stub, clientID:clientID, args: args}, nil
}

func (ctx *Context) PutState(key string, val []byte) error {
	return ctx.stub.PutState(key, val)
}

func (ctx *Context) GetState(key string) ([]byte, error) {
	return ctx.stub.GetState(key)
}

func (ctx *Context) DelState(key string) error {
	return  ctx.stub.DelState(key)
}

func (ctx *Context) SetEncryptState(key string, value []byte) error {
	// TODO
	if ctx.bccspInst == nil {
		return fmt.Errorf("bccsp package not initiate")
	}
	tMap, err := ctx.stub.GetTransient()
	if err != nil {
		return err
	}
	encKey, _ := tMap["encKey"]
	if encKey == nil {
		return fmt.Errorf("not found encKey")
	}
	ivKey, _ := tMap["iv"]
	encEntry, err := entities.NewAES256EncrypterEntity("Host", ctx.bccspInst, encKey, ivKey)
	if err != nil {
		return fmt.Errorf("new encrypo entity error:%v", err)
	}
	cipherText, err := encEntry.Encrypt(value)
	if err != nil {
		return err
	}
	return ctx.PutState(key, cipherText)
}

func (ctx *Context) GetDecryptState(key string) ([]byte, error) {
	// TODO
	tMap, err := ctx.stub.GetTransient()
	if err != nil {
		return nil, err
	}
	decKey, _ := tMap["decKey"]
	if decKey == nil {
		return nil, fmt.Errorf("not found decKey")
	}
	iv, _ := tMap["iv"]
	decEntry, err := entities.NewAES256EncrypterEntity("Host", ctx.bccspInst, decKey, iv)
	if err != nil {
		return nil, err
	}
	ciphertext, _ := ctx.GetState(key)
	if len(ciphertext) == 0 {
		return nil, err
	}
	return decEntry.Decrypt(ciphertext)
}

func (ctx *Context) SetValueEncryptAndSigner(key string, value []byte) error {
	// TODO
	return nil
}

func (ctx *Context) GetValueVerifyAndDecrypt(key string) ([]byte, error) {
	// TODO
	return nil, nil
}
func (ctx *Context) PutAsset(key string, asset Asset) error {
	data, err := json.Marshal(asset)
	if err != nil {
		return err
	}
	return ctx.stub.PutState(key, data)
}

func (ctx *Context) GetAsset(key string, asset Asset) error {
	data, err := ctx.GetState(key)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, asset)
}

var ErrArgNotExist = errors.New("arg not exist")

func (ctx Context) GetStringArg(key string) (string, error) {
	val, ok := ctx.args[key]
	if !ok {
		return "", ErrArgNotExist
	}
	return val.(string), nil
}

func (ctx Context) GetIntArg(key string) (int, error) {
	val, ok := ctx.args[key]
	if !ok {
		return 0, ErrArgNotExist
	}
	return val.(int), nil
}

func (ctx Context) GetStructArg(key string, obj interface{}) error {
	// TODO: get struct value
	return nil
}

func (ctx *Context) GetMSPID() (string, error) {
	return ctx.clientID.GetMSPID()
}

func (ctx *Context) GetID() (string, error) {
	return ctx.clientID.GetID()
}

func (ctx Context) GetCertificate() (*x509.Certificate, error) {
	return ctx.clientID.GetX509Certificate()
}

func (ctx Context) GetAttributeValue(attrName string) (string, error) {
	val, found, err := ctx.clientID.GetAttributeValue(attrName)
	if err != nil {
		return "", err
	}
	if !found {
		return "", fmt.Errorf("not found")
	}
	return val, nil
}

func (ctx Context) Success(payload []byte) Response {
	return Response{State:200, Payload:payload}
}

func (ctx Context) Error(err error) Response {
	return Response{State:500, Message:err.Error()}
}

func (ctx Context) ErrorState(state int32, err error) Response {
	return Response{State:state, Message:err.Error()}
}